import java.util.ArrayList;
import java.util.List;

/**
 * Classe que executa as ações semânticas da linguagem intermediária iterativa.
 * O resultado é representado por listas de comandos que identificam os rótulos
 * e ações que devem ser executados. Esse resultado é então convertido em
 * instruções rotuladas (programa monolítico).
 * 
 * @author Douglas Ruskowski Haase
 * 
 */
public class SemanticoIterativo implements Constants {

    private int enderecoAtual = 0;

    private PilhaRotulos pilhaRotulos = new PilhaRotulos();

    private PilhaComandos pilhaEqtos = new PilhaComandos();

    private PilhaComandos pilhaIfs = new PilhaComandos();

    private RepresentacaoIterativa iterativa = new RepresentacaoIterativa();

    private List<Integer> enderecosPendentesBlocoRep = new ArrayList<Integer>();

    public void executeAction(int action, Token token) throws SemanticError {

	switch (action) {

	case 1:
	    actionOne(token);
	    break;

	case 2:
	    actionTwo(token);
	    break;

	case 3:
	    actionThree(token);
	    break;

	case 4:
	    actionFour(token);
	    break;

	case 5:
	    actionFive(token);
	    break;

	case 6:
	    actionSix(token);
	    break;

	case 7:
	    actionSeven(token);
	    break;

	case 8:
	    actionEight(token);
	    break;

	case 9:
	    actionNine(token);
	    break;

	case 10:
	    actionTen(token);
	    break;

	case 11:
	    actionEleven(token);
	    break;

	default:
	    break;
	}

    }

    // Ação 1: incrementa '1' no endereço que inicia em '0'.
    private void actionOne(Token token) throws SemanticError {
	incrementaEndereco();
    }

    /**
     * Terminou (TESTE).
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionTwo(Token token) {

	// C4(pop(), ender)
	// int entao = pilha.pop();

	NoPilhaElementos no = pilhaRotulos.popNoPilha();

	// C4(pop(), ender)
	// atualiza a saída do então caso não seja um comando de bloco-rep
	if (!no.isBlocoRep()) {
	    iterativa.getC4().put(no.getValor(), enderecoAtual);
	}

	// se tem ifs e se o numero de ifs abertos for igual ao numero de eqtos,
	// desempilha um comando eqto (mais interno)
	// if ((pilhaIfs.tamanho() > 0)
	// && (pilhaIfs.tamanho() == pilhaEqtos.tamanho())) {
	// retira eqto
	pilhaEqtos.pop();
	// }

	// retira if
	pilhaIfs.pop();

	// atualiza sucessor do bloco-rep: não pode sair do bloco-rep e ir prum
	// senão, logo, atualizo essa informação após sair do bloco-if.
	for (Integer i : enderecosPendentesBlocoRep) {
	    iterativa.getC5().put(i, enderecoAtual);
	}

	enderecosPendentesBlocoRep.clear();
    }

    /**
     * Terminou ATE COND.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionThree(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1= teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C5(endereco, sucessor)
	iterativa.getC5().put(enderecoAtual, enderecoAtual + 1);

	// Push(endereco)
	pilhaRotulos.push(enderecoAtual);

	incrementaEndereco();

    }

    /**
     * Ação 4: terminou BLOCO-REP do ATE.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionFour(Token token) {

	// C4(front(), endereco) atualiza saida do ATE
	int ate = pilhaRotulos.getPrim();
	iterativa.getC4().put(ate, enderecoAtual);

	// C4(pred, pop()) atualiza a próxima instrução do último comando do
	// bloco 'ate'
	int saidaAte = pilhaRotulos.pop();
	iterativa.getC4().put(getEnderecoPredecessor(), saidaAte);

    }

    /**
     * Ação 5: terminou EQTO COND.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionFive(Token token) {

	// C0(endereco)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1 = teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	// Push(endereco)
	pilhaRotulos.push(enderecoAtual);
	// aqui é o push do indice 4 q tenho q arrumar o va_para 8
	// pilha.pushNo(enderecoAtual, blocoRep);

	pilhaEqtos.push("eqto", enderecoAtual);

	incrementaEndereco();
    }

    /**
     * Ação 6: terminou BLOCO-REP do EQTO.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionSix(Token token) {

	// C5(front(), ender) lê topo da pilha e atualiza esse endereço com o
	// atual no C5
	int enquanto = pilhaRotulos.getPrim();

	// se tiver dentro de um TESTE, salva o valor pra atualizar na saída do
	// teste. bloco rep não pode ir pra proxima instrução se ela for SENAO
	if (pilhaIfs.vazia() || (pilhaIfs.tamanho() > pilhaEqtos.tamanho())) {
	    iterativa.getC5().put(enquanto, enderecoAtual);

	} else {
	    enderecosPendentesBlocoRep.add(enquanto);
	}

	// C4(pred, pop()) atualiza a próxima instrução do último comando do
	// bloco 'enquanto'
	int saidaEqto = pilhaRotulos.pop();
	iterativa.getC4().put(getEnderecoPredecessor(), saidaEqto);

    }

    /**
     * 
     * Ação 7: encontrou um id no não-terminal CMD.
     * 
     * {C0(ender,ender)} = cria operação
     * 
     * {C2(ender, nr)} = receptor (registrador que será incrementado ou
     * decrementado 1)
     * 
     * {C4(ender, succ)} = aponta para o próximo comando
     * 
     * @param token
     *            o token lido nessa ação.
     * 
     */
    private void actionSeven(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C2(nr)
	String stringRegistrador = token.getLexeme().substring(1,
		token.getLexeme().length());

	iterativa.getC2()
		.put(enderecoAtual, Integer.valueOf(stringRegistrador));

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);
    }

    /**
     * Ação 8: encontrou + ou - como operação de um id.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionEight(Token token) {

	// C1(3 ou 4) 3 = incremento; 4 = decremento
	String operacao = token.getLexeme();

	if (operacao.equals("+")) {
	    iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.INCREMENTO);

	} else {
	    iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.DECREMENTO);
	}

	incrementaEndereco();
    }

    /**
     * Ação 9: terminou 'SE COND' no teste.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionNine(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1 = teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	// Push(ender)
	pilhaRotulos.push(enderecoAtual);

	// empilha if
	pilhaIfs.push("if", enderecoAtual);

	incrementaEndereco();

    }

    /**
     * Ação 10: encontrou SENAO
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionTen(Token token) {

	// C5(pop(), ender) atualiza 'senao' do teste
	int senao = pilhaRotulos.pop();
	iterativa.getC5().put(senao, enderecoAtual);

	// empilhar fim do entao. se o final do entao for o ultimo comando de um
	// bloco rep, nao atualiza o c4 dele, marca a flag como true. assim, o
	// valor permanecerá retornando ao inicio do eqto. se a pilha
	// de eqtos estiver vazia ou tiver mais ifs abertos q o numero de eqtos
	// abertos, significa que pode sim atualizar o C4 (sucessor) e sair do
	// bloco q é apenas um if mais interno e nao um eqto, deixa atualizar
	// marcando como false.
	if (pilhaEqtos.vazia() || (pilhaIfs.tamanho() > pilhaEqtos.tamanho())) {
	    pilhaRotulos.pushNo(getEnderecoPredecessor(), false);
	} else {
	    pilhaRotulos.pushNo(getEnderecoPredecessor(), true);
	}

    }

    /**
     * Ação 11: encontrou 'COND' no teste.
     * 
     * @param token
     */
    private void actionEleven(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(2) 2 = atribuição de registrador
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.ATRIBUICAO);

	// C2(0) registrador de teste
	iterativa.armazenaReceptorC2(enderecoAtual, 0);

	// C3(nr)
	String valorRegistrador = token.getLexeme().substring(1,
		token.getLexeme().length());

	iterativa.armazenaSenderC3(enderecoAtual,
		Integer.valueOf(valorRegistrador));

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	incrementaEndereco();

    }

    /**
     * Incrementa em 1 o valor do endereço atual.
     */
    private void incrementaEndereco() {
	enderecoAtual++;
    }

    private int getEnderecoPredecessor() {
	return enderecoAtual - 1;
    }

    public RepresentacaoIterativa getIterativa() {
	return iterativa;
    }

}
